/****************************************************************************
 * Stage 2 of the installation.
 * Set a hook and hot reboot the OS to get it back to a stable state.
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Ndless code.
 *
 * The Initial Developer of the Original Code is Fabian Vogt.
 * Portions created by the Initial Developer are Copyright (C) 2013-2014
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Excale, Olivier ARMAND <olivier.calc@gmail.com>.
 * 
 ****************************************************************************/

#include <limits.h>
#include <string.h>
#include <stdint.h>
#include <hook.h>
#include <nucleus.h>
#include <syscall-list.h>
#include <syscall-addrs.h>
#include <syscall.h>

extern "C" {
extern int ut_os_version_index;
int sc_addrs_ptr; // Required by utils.c
void ut_disable_watchdog();
void ut_read_os_version_index();
void ut_disable_watchdog();
}

void clear_cache()
{
	unsigned dummy;
	__asm volatile(
		"0: mrc p15, 0, r15, c7, c10, 3 @ test and clean DCache \n"
		" bne 0b \n"
		" mov %0, #0 \n"
		" mcr p15, 0, %0, c7, c7, 0 @ invalidate ICache and DCache \n" : "=r" (dummy));
}

// As expected by the patch headers generated by MakeHotRebootPtch
#define PATCH_SETW(A,B)   *(uint32_t *)(A) = (B)
#define PATCH_SETZ(A,B,C) memset32((uint32_t *)(A), (C), (B)-(A))

#define RES_PATH_REL "./ndless/ndless_resources.tns"

static void memset32(uint32_t *p, uint32_t value, size_t num) {
	num = num/4;
	while(num--) {
		*(p++) = value;
	}
}

static void write_i2c(uint8_t client, uint8_t addr, uint8_t value) {
	PATCH_SETW(0x9005006c, 0); //Disable I2C
	PATCH_SETW(0x90050004, client); //Set target address
	PATCH_SETW(0x9005006c, 1); //Enable I2C

	volatile uint32_t *status = (uint32_t*) 0x90050070;
	
	PATCH_SETW(0x90050010, addr);
	while(*status & 1); //Wait until transmitted
	PATCH_SETW(0x90050010, value);
	while(*status & 1); //Wait until transmitted
}

static void write_touchpad(uint16_t port, uint8_t value) {
	write_i2c(0x20, 0xFF, port >> 8);
	write_i2c(0x20, port & 0xFF, value);
}

// OS-specific
static unsigned const ndless_inst_resident_hook_addrs[] = {0x10012598, 0x1001251C, 0x100123BC, 0x10012370};

// Install the resident part
HOOK_DEFINE(s1_startup_hook) {
	struct nuc_stat res_stat;
	NUC_FILE *res_file;
	char *core;
	char *res_params = NULL;

	syscall_local<e_NU_Set_Current_Dir, void>(syscall_local<e_get_documents_dir, const char*>());

	if (!(res_file = syscall_local<e_fopen, NUC_FILE*>(RES_PATH_REL, "rb"))) {
		int x = 0;
		syscall_local<e_disp_str, void>("Oops, you've forgotten to transfer                   'ndless_resources.tns'! Ndless won't be installed.", &x, 10);
		volatile int i;
		for (i = 0; i < 100000000; i++) // libndls's sleep() requires is_classic, not available here
			;
		goto s1_startup_hook_return;
	}
	syscall_local<e_stat, int>(RES_PATH_REL, &res_stat);
	core = syscall_local<e_malloc, char*>(res_stat.st_size);
	syscall_local<e_fread, int>(core, res_stat.st_size, 1, res_file);
	syscall_local<e_fclose, int>(res_file);
	clear_cache();
	((int (*)(int argc, void* argv))(core + sizeof("PRG")))(1, &res_params); // Run the core installation
s1_startup_hook_return:
	HOOK_UNINSTALL(ndless_inst_resident_hook_addrs[ut_os_version_index - 6], s1_startup_hook);
	HOOK_RESTORE_RETURN(s1_startup_hook);
}

int main() {
	ut_disable_watchdog();
	ut_read_os_version_index();
	ut_disable_watchdog();

	//OS 3.6 starts at 6
	const int version_index_36 = ut_os_version_index - 6;
		
	// Disable all interrupts
	if (version_index_36 < 2) {
			PATCH_SETW(0xDC00000C, 0xFFFFFFFF);
	} else {
	PATCH_SETW(0xDC000014, 0xFFFFFFFF);
	}
	if (version_index_36 < 2) {
		uint32_t RX;
		__asm volatile (
			"mrs %0, cpsr      \n"
			"bic %0, %0, #0x80 \n"
			"msr cpsr_c, %0    \n"
		: "=r" (RX));
	}
	if (version_index_36 < 2) {
			uint32_t dummyint = *((volatile uint32_t *)(0xDC000028));
			(void)dummyint; // unused warning
	}
	
	// Reset IRQ flags
	if (version_index_36 > 1) {
			PATCH_SETW(0x90010008, 0);
			PATCH_SETW(0x90010008, 0);
			PATCH_SETW(0x9001000C, 1);
			PATCH_SETW(0x90010028, 0);
			PATCH_SETW(0x9001002C, 1);
			PATCH_SETW(0x900C0008, 0);
			PATCH_SETW(0x900C000C, 1);
			PATCH_SETW(0x900C0028, 0);
			PATCH_SETW(0x900C002C, 1);
			PATCH_SETW(0x900D0008, 0);
			PATCH_SETW(0x900D000C, 1);
			PATCH_SETW(0x900D0028, 0);
			PATCH_SETW(0x900D002c, 1);
	}
		
	//Reset USB
	volatile int z;
	PATCH_SETW(0x900B0018, *(volatile uint32_t*)0x900B0018 & 0b11111111111111111111111110011111);
	for (z = 0; z <= 0x10000; z++)
		;
	uint32_t usb_cmd = *((volatile uint32_t *)(0xB0000140));
	usb_cmd &= ~1;
	PATCH_SETW(0xB0000140, usb_cmd);
	PATCH_SETW(0xB4000140, usb_cmd);
	for (z = 0; z <= 0x10000; z++)
		;
	usb_cmd = *((volatile uint32_t *)(0xB0000140));
	usb_cmd |= 0x2;
	PATCH_SETW(0xB0000140, usb_cmd);
	while ( ((*((volatile uint32_t *)(0xB0000140)))&0x2) != 0x0 )
			;
	PATCH_SETW(0xB00001A4, 0x003C1120);

	if (version_index_36 > 1) {
		// Reset touchpad
		write_touchpad(0x0004, 0x01);
		// Disable I2C IRQ
		PATCH_SETW(0x90050030, 0);
		// Disable I2C
		PATCH_SETW(0x9005006C, 0);
	}
	// Disable RTC IRQ
	PATCH_SETW(0x9009000C, 1);

	// Disable keypad and touchpad IRQs
	PATCH_SETW(0x900E000C, 0);
	PATCH_SETW(0x900E0040, 0);

	// Reset OS global variables to their initial values
	// Reset internal RAM state, else unstable without USB plugged-in
	switch (version_index_36) {
		case 0:
				#include "hrpatches-os-ncas-3.6.0.h"
		break;
		case 1:
				#include "hrpatches-os-cas-3.6.0.h"
		 break;
		case 2:
				#include "hrpatches-os-ncascx-3.6.0.h"
		break;
		case 3:
				#include "hrpatches-os-cascx-3.6.0.h"
		break;
	}

	// disable the OS monitor thread that would throw a discrepancy error and wipe out the OS
	// this thread use signature data passed by the boot2 and copied to the first OS variable at the beginning of the BSS
	// this signature data may have been overwritten (and is always on classic TI-Nspire after opening the Lua installer)
	// OS-specific
	if (version_index_36 < 2) {
		static unsigned const os_monitor_thread_addrs[] = {0x10135DF4, 0x10136418};
		PATCH_SETW(os_monitor_thread_addrs[version_index_36], 0xE12FFF1E); // "bx lr" at the beginning of the thread
	}

	// post hot-reboot hook
	HOOK_INSTALL(ndless_inst_resident_hook_addrs[version_index_36], s1_startup_hook);
	
	clear_cache();
	((void(*)(void))0x10000000)(); // Hot-reboot the OS
	__builtin_unreachable();
}
